import fs from 'fs';
import path from 'path';
import process from 'process';
import inquirer from 'inquirer';
import {StrUtil} from '../../../utils/StrUtil.js';
import {GitUtil} from '../../../utils/GitUtil.js';
import {FileUtil} from '../../../utils/FileUtil.js';
import {AppUtil} from '../../../utils/AppUtil.js';
import {OPSConstant} from '../../../constant/OPSConstant.js';
import {initProjectOpsFile, getProjectOpsFile} from '../../../utils/OpsFile.js';

export const initMixin = {
    async initNormal(url, flowId, flowType, directory, cloneOptions = {}) {
        const codePath = await this.globalOpsFile.read('code_path');
        if (StrUtil.isNull(codePath)) {
            throw new Error("请检查文件 ops_global文件中的 code_path 属性，请维护你期望下载的位置");
        }
        const resolvedCodePath = path.resolve(codePath);
        const currentDir = process.cwd();
        const flowDetail = await this.opsClient.get_flow_detail(flowId, flowType);
        let workItemName = StrUtil.toSafeFolderName(flowDetail.work_item_name);
        let refresh = false;
        if (StrUtil.isNull(directory)) {
            if (flowType === 'story') {
                // 需求有开发线 中台开发
                const devPortLabel = flowDetail.dev_port_label;
                workItemName = `${flowId}_${workItemName}_${devPortLabel}`
            } else if (flowType === 'issue') {
                // bug  这个字段是中台
                const productOwn = flowDetail.product_own;
                workItemName = `${flowId}_${workItemName}_${productOwn}`
            }
            directory = path.join(resolvedCodePath, workItemName);
            this.logger.info(`您未指定下载目录，项目将下载到系统默认目录+发布名字：${directory}`)
        } else {
            if (directory === 'PWD' || directory === 'pwd') {
                directory = currentDir;
                refresh = true;
                this.logger.info(`您指定当前目录下载目录，项目将下载到 ${currentDir}`)
            } else {
                directory = path.join(resolvedCodePath, directory);
                this.logger.info(`您已经指定下载目录，项目将下载到：${directory}`)
            }
        }
        directory = path.resolve(directory);
        FileUtil.ensureDir(directory)
        // 初始化文件
        this.assignProjectOpsFile(initProjectOpsFile(directory));
        // 追加，1 普通发布单   2 依赖发布单 3 发布计划
        await this.projectOpsFile.write("project.type", 1, true);
        await this.projectOpsFile.write("project.chat_id", "oc_8e46f2cdf24aa7cf3d91e54ba391aa33");
        await this.projectOpsFile.writeIfAbsent("notice.feishu.notify_dev", "true");
        await this.projectOpsFile.writeIfAbsent("notice.feishu.notify_test", "true");
        await this.projectOpsFile.write("project.typeStr", "普通发布单", true);
        // 发布单 id
        await this.projectOpsFile.writeIfAbsent("flow.id", flowId);
        // story 需求  issue bug
        await this.projectOpsFile.writeIfAbsent("flow.flow_type", flowType);
        // 原始下载的 url
        await this.projectOpsFile.writeIfAbsent("flow.url", url);
        // 发布单名字
        await this.projectOpsFile.writeIfAbsent("flow.ops_flow_name", flowDetail.work_item_name);
        const appAndBranchData = await this.opsClient.getAppAndBranch(flowId, flowType);
        this.logger.step(2, `准备clone项目: ${appAndBranchData.length}`);
        const appIds = [];
        const appNames = [];
        const appJzNames = [];
        const appJzPluginNames = [];
        for (const item of appAndBranchData) {
            // git地址
            const repoUrl = this.resolveRepoUrlForClone(item.repo_url, cloneOptions);
            // 带下划线的 有的没带
            const appName = item.app;
            // 数字的 id，每个发布单单独的
            const appId = item.id;
            appIds.push(appId);
            appNames.push(appName);
            // 分支
            const branch = item.name;
            // 项目文件名字，如果有子目录就 就是 子目录的名字
            let repoName = StrUtil.getRepoName(repoUrl);
            let orgRepoName = repoName;
            // 是否是基座
            const plugin = StrUtil.hasValue(item, 'plugin_list') && item.plugin_list.length > 0;
            if (plugin) {
                FileUtil.ensureDir(`${directory}/${appName}`);
                // 当前基座下的插件项目
                let pluginAppNameArr = [];
                // 插件项目
                for (const pluginItem of item.plugin_list) {
                    // 插件项目 git地址
                    const pluginRepoUrl = this.resolveRepoUrlForClone(pluginItem.repo_url, cloneOptions);
                    const repoId = pluginItem.id;
                    const pluginBranch = pluginItem.name;
                    // hs-crm-admin-base
                    const pluginAppName = pluginItem.app;
                    pluginAppNameArr.push(pluginAppName);
                    appJzPluginNames.push(pluginAppName);
                    let pluginRepoName = StrUtil.getRepoName(pluginRepoUrl);
                    let orgPluginRepoName = pluginRepoName;
                    let pluginAppNamePath = `${appName}/${pluginRepoName}`;
                    let plArg = null;
                    if (StrUtil.hasValue(pluginItem, 'common') && StrUtil.hasValue(pluginItem.common, 'command')) {
                        // 打包指令
                        const pluginPlName = StrUtil.getPlParam(pluginItem.common.command);
                        // 有这个-pl 参数说明当前是个 pom 父项目，然后靠 pl 参数指定子项目
                        if (StrUtil.notNull(pluginPlName)) {
                            pluginRepoName = pluginPlName;
                            pluginAppNamePath += `/${pluginPlName}`;
                            plArg = pluginPlName;

                        }
                    }
                    if (StrUtil.notNull(plArg) && plArg != null) {
                        const plArr = plArg.split(",");
                        for (const plApp of plArr) {
                            const {last, before} = StrUtil.splitPath(plApp);
                            if (StrUtil.notNull(last)) {
                                let key = StrUtil.getLastSegment(last);
                                await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${key}.opsName`, pluginAppName);
                                // 插件对应对应基座 的appid
                                await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${key}.father`, appName);
                                await this.projectOpsFile.write(`flow_jz_plugin.${key}.flow_app_id`, repoId);
                                await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${key}.source.branch`, pluginBranch);
                                const result = StrUtil.serviceToServer(key);
                                if (result) {
                                    await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${result}.opsName`, pluginAppName);
                                    await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${result}.father`, appName);
                                    await this.projectOpsFile.write(`flow_jz_plugin.${result}.flow_app_id`, repoId);
                                    await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${result}.source.branch`, pluginBranch);
                                }
                            }
                            if (StrUtil.notEmptyArray(before)) {
                                for (const spAppName of before) {
                                    let key = StrUtil.getLastSegment(spAppName);
                                    await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${key}.opsName`, pluginAppName);
                                    // 插件对应对应基座 的appid
                                    await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${key}.father`, appName);
                                    await this.projectOpsFile.write(`flow_jz_plugin.${key}.flow_app_id`, repoId);
                                    await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${key}.source.branch`, pluginBranch);
                                    const result = StrUtil.serviceToServer(key);
                                    if (result) {
                                        await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${result}.opsName`, pluginAppName);
                                        await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${result}.father`, appName);
                                        await this.projectOpsFile.write(`flow_jz_plugin.${result}.flow_app_id`, repoId);
                                        await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${result}.source.branch`, pluginBranch);
                                    }
                                }
                            }
                        }
                    } else {
                        await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${pluginRepoName}.opsName`, pluginAppName);
                        // 插件对应对应基座 的appid
                        await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${pluginRepoName}.father`, appName);
                        await this.projectOpsFile.write(`flow_jz_plugin.${pluginRepoName}.flow_app_id`, repoId);
                        await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${pluginRepoName}.source.branch`, pluginBranch);
                        const result = StrUtil.serviceToServer(pluginRepoName);
                        if (result) {
                            await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${result}.opsName`, pluginAppName);
                            await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${result}.father`, appName);
                            await this.projectOpsFile.write(`flow_jz_plugin.${result}.flow_app_id`, repoId);
                            await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${result}.source.branch`, pluginBranch);
                        }
                    }
                    // 该项目再基座里面 文件夹名字=基座名字
                    await this.projectCacheFile.writeIfAbsent(`jz.${pluginRepoName}`, appName);
                    // 该项目再基座里面 文件夹名字=基座名字下的名字
                    await this.projectCacheFile.writeIfAbsent(`jz_plugin.${pluginRepoName}`, pluginAppName);
                    // 插件对应对应基座 的appid
                    await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${pluginAppName}.path`, pluginRepoName);
                    // hs-crm-admin-base 这种名字对应具体项目的位置
                    await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${pluginAppName}.git.path`, `${pluginAppNamePath}`);
                    await this.projectOpsFile.writeIfAbsent(`flow_jz_plugin.${pluginAppName}.source.branch`, pluginBranch);
                    await GitUtil.clone(pluginRepoUrl, pluginBranch, `${directory}/${appName}/${orgPluginRepoName}`, cloneOptions);
                }
                // 保存基座的信息 repoName=文件夹名字   appName ops 上面的，可能有下划线
                await this.projectOpsFile.writeIfAbsent(`flow_jz.${repoName}.opsName`, appName);
                await this.projectOpsFile.write(`flow_jz.${repoName}.flow_app_id`, appId);
                await this.projectOpsFile.write(`flow_jz.${appName}.flow_app_id`, appId);
                // 基座名字
                await this.projectOpsFile.writeIfAbsent(`flow_jz.${appName}.path`, repoName);
                // 基座git信息
                await this.projectOpsFile.writeIfAbsent(`flow_jz.git.${appName}`, `${repoUrl}`);
                // 基座版本，master 或者 versionxxx
                await this.projectOpsFile.writeIfAbsent(`flow_jz.version.${appName}`, `${branch}`);
                // 保存基座下的项目
                await this.projectOpsFile.writeList(`flow_jz.${appName}.plugins`, pluginAppNameArr);
                appJzNames.push(appName);
                continue;
            }
            let plArg = null;
            if (StrUtil.hasValue(item, 'common') && StrUtil.hasValue(item.common, 'command')) {
                // 打包指令
                const plName = StrUtil.getPlParam(item.common.command);
                // 有这个-pl 参数说明当前是个 pom 父项目，然后靠 pl 参数指定子项目
                if (StrUtil.notNull(plName)) {
                    repoName = plName;
                    plArg = plName;
                }
            }
            if (StrUtil.notNull(plArg) && plArg != null) {
                const plArr = plArg.split(",");
                for (const plApp of plArr) {
                    const {last, before} = StrUtil.splitPath(plApp);
                    if (StrUtil.notNull(last)) {
                        let key = StrUtil.getLastSegment(last);
                        await this.projectCacheFile.writeIfAbsent(`app.${key}`, appName);
                        await this.projectOpsFile.writeIfAbsent(`flow.${key}.opsName`, appName);
                        await this.projectOpsFile.writeIfAbsent(`flow.${key}.source.branch`, branch);
                        await this.projectOpsFile.write(`flow.${key}.flow_app_id`, appId);
                        const result = StrUtil.serviceToServer(key);
                        if (result) {
                            await this.projectOpsFile.writeIfAbsent(`flow.${result}.opsName`, appName);
                            await this.projectOpsFile.writeIfAbsent(`flow.${result}.source.branch`, branch);
                            await this.projectOpsFile.write(`flow.${result}.flow_app_id`, appId);
                        }
                    }
                    if (StrUtil.notEmptyArray(before)) {
                        for (const spAppName of before) {
                            let key = StrUtil.getLastSegment(spAppName);
                            await this.projectCacheFile.writeIfAbsent(`app.${key}`, appName);
                            await this.projectOpsFile.writeIfAbsent(`flow.${key}.opsName`, appName);
                            await this.projectOpsFile.writeIfAbsent(`flow.${key}.source.branch`, branch);
                            await this.projectOpsFile.write(`flow.${key}.flow_app_id`, appId);
                            const result = StrUtil.serviceToServer(key);
                            if (result) {
                                await this.projectOpsFile.writeIfAbsent(`flow.${result}.opsName`, appName);
                                await this.projectOpsFile.writeIfAbsent(`flow.${result}.source.branch`, branch);
                                await this.projectOpsFile.write(`flow.${result}.flow_app_id`, appId);
                            }
                        }
                    }
                }
            } else {
                // 再次保存 和上面的一样的关系
                await this.projectOpsFile.writeIfAbsent(`flow.${repoName}.opsName`, appName);
                await this.projectCacheFile.writeIfAbsent(`app.${repoName}`, appName);
                await this.projectOpsFile.write(`flow.${repoName}.flow_app_id`, appId);
                await this.projectOpsFile.write(`flow.${appName}.flow_app_id`, appId);
                // 原发布单分支
                await this.projectOpsFile.writeIfAbsent(`flow.${repoName}.source.branch`, branch);
                const result = StrUtil.serviceToServer(repoName);
                if (result) {
                    await this.projectOpsFile.writeIfAbsent(`flow.${result}.opsName`, appName);
                    await this.projectOpsFile.write(`flow.${result}.flow_app_id`, appId);
                    await this.projectOpsFile.writeIfAbsent(`flow.${result}.source.branch`, branch);
                    await this.projectCacheFile.writeIfAbsent(`app.${result}`, appName);
                }
            }

            // 反向保存一次
            await this.projectOpsFile.writeIfAbsent(`flow.${appName}.path`, repoName);
            await this.projectOpsFile.write(`flow.${appName}.flow_app_id`, appId);

            // 多层项目的是一，这里是git的根目录
            await this.projectOpsFile.writeIfAbsent(`flow.${appName}.git.path`, orgRepoName);

            await this.projectOpsFile.writeIfAbsent(`flow.${appName}.source.branch`, branch);
            await GitUtil.clone(repoUrl, branch, `${directory}/${orgRepoName}`, cloneOptions);
        }
        if (appJzNames.length > 0) {
            await this.projectOpsFile.writeList('flow.jz.app.names', appJzNames);
        }
        if (appJzPluginNames.length > 0) {
            await this.projectOpsFile.writeList('flow.jz.plugin.app.names', appJzPluginNames);
        }
        if (appIds.length > 0) {
            await this.projectOpsFile.writeList('flow.app.ids', appIds);
            await this.projectOpsFile.writeList('flow.app.names', appNames);
        }
        if (refresh) {
            this.logger.success("项目刷新完成");
        } else {
            this.logger.success("项目初始化完成");
            this.scheduleOpenIdea(directory);
        }


    },

    async initDep(url, id, directory, cloneOptions = {}) {
        this.logger.info("依赖发布单 id:", id);
        const codePath = await this.globalOpsFile.read('code_path');
        if (StrUtil.isNull(codePath)) {
            throw new Error("请检查文件 ops_global文件中的 code_path 属性，请维护你期望下载的位置");
        }
        const releaseApp = await this.opsClient.releaseApp(id);
        if (StrUtil.isEmptyArray(releaseApp)) {
            throw new Error("项目信息不存在，请检查依赖发布单是不是空的？");
        }
        const currentDir = process.cwd();

        // 第一个
        const fApp = releaseApp[0];
        let supportName = fApp.name;
        if (StrUtil.isNull(directory)) {
            directory = `${codePath}/${supportName}`;
            this.logger.info(`您未指定下载目录，项目将下载到系统默认目录+发布名字：${supportName}`)
        }
        if (directory === 'PWD' || directory === 'pwd') {
            directory = currentDir;
            this.logger.info(`您指定当前目录下载目录，项目将下载到 ${currentDir}`)
        } else {
            directory = `${codePath}/${directory}`;
            this.logger.info(`您已经指定下载目录，项目将下载到：${directory}`)
        }
        FileUtil.ensureDir(directory)
        // 初始化文件
        this.assignProjectOpsFile(initProjectOpsFile(directory));
        //1 普通发布单   2 依赖发布单 3 发布计划
        await this.projectOpsFile.write("project.type", 2, true);
        await this.projectOpsFile.writeIfAbsent("notice.feishu.notify_dev", "true");
        await this.projectOpsFile.writeIfAbsent("notice.feishu.notify_test", "true");
        await this.projectOpsFile.write("project.typeStr", "依赖发布单", true);
        await this.projectOpsFile.writeIfAbsent("support.id", id);
        await this.projectOpsFile.writeIfAbsent("support.url", url);
        await this.projectOpsFile.writeIfAbsent("support.name", fApp.name);
        const appNames = [];

        for (const app of releaseApp) {
            const repoUrl = this.resolveRepoUrlForClone(app.scm, cloneOptions);
            let repoName = StrUtil.getRepoName(repoUrl);
            const rawAppName = app.appname;
            const trimmedAppName = rawAppName ? String(rawAppName).trim() : '';
            // 接口可能返回 name=alias 形式，这里只保留原始应用名
            const normalizedAppName = StrUtil.normalizeOpsAppName(trimmedAppName);
            const appName = normalizedAppName || trimmedAppName || repoName;
            if (!appName) {
                const releaseId = app && typeof app.id !== 'undefined' ? app.id : 'unknown';
                this.logger.warn(`接口返回的应用名称与仓库名均为空，已跳过 releaseId=${releaseId}`);
                continue;
            }
            if (normalizedAppName && normalizedAppName !== trimmedAppName) {
                this.logger.debug(`应用名称 ${trimmedAppName} 清洗为 ${normalizedAppName}`);
            }
            appNames.push(appName);
            const branch = app.branch;
            let plArg = null;
            if (StrUtil.hasValue(app, 'common') && StrUtil.hasValue(app.common, 'command')) {
                // 打包指令
                const plName = StrUtil.getPlParam(app.common.command);
                // 有这个-pl 参数说明当前是个 pom 父项目，然后靠 pl 参数指定子项目
                if (StrUtil.notNull(plName)) {
                    plArg = plName;
                }
            }
            if (StrUtil.notNull(plArg) && plArg != null) {
                const plArr = plArg.split(",");
                for (const plApp of plArr) {
                    const {last, before} = StrUtil.splitPath(plApp);
                    if (StrUtil.notNull(last)) {
                        const key = StrUtil.getLastSegment(last);
                        await this.projectCacheFile.writeIfAbsent(`support.${key}`, appName);
                        await this.projectOpsFile.writeIfAbsent(`support.${key}.opsName`, appName);
                        await this.projectOpsFile.writeIfAbsent(`support.${key}.rid`, app.id);
                        await this.projectOpsFile.writeIfAbsent(`support.${key}.source.branch`, branch);

                    }
                    if (StrUtil.notEmptyArray(before)) {
                        for (const spAppName of before) {
                            const key = StrUtil.getLastSegment(spAppName);
                            await this.projectCacheFile.writeIfAbsent(`support.${key}`, appName);
                            await this.projectOpsFile.writeIfAbsent(`support.${key}.opsName`, appName);
                            await this.projectOpsFile.writeIfAbsent(`support.${key}.rid`, app.id);
                            await this.projectOpsFile.writeIfAbsent(`support.${key}.source.branch`, branch);
                        }
                    }

                }
            } else {
                await this.projectOpsFile.writeIfAbsent(`support.${repoName}.rid`, app.id);
                await this.projectOpsFile.writeIfAbsent(`support.${repoName}.source.branch`, branch);
                await this.projectOpsFile.writeIfAbsent(`support.${repoName}.opsName`, appName, true);
            }
            const targetDir = path.join(directory, repoName);
            const reusePath = this.findReusableRepoPath(repoName, {
                searchRoot: directory,
                targetDir
            });
            const recordedPath = reusePath || repoName;
            // 保存 原始文件夹和 ops 名字(奇奇怪怪的命名，可能有下划线)，方便下回使用，直接读取这个配置即可
            if (reusePath) {
                await this.projectOpsFile.write(`support.${appName}.path`, recordedPath);
                this.logger.info(
                    `检测到同名目录已存在，已复用并跳过克隆: ${reusePath}（可在 ops_global.properties 设置 clone.reuse_existing_dir=false 关闭复用）`
                );
                continue;
            }
            await this.projectOpsFile.writeIfAbsent(`support.${appName}.path`, recordedPath);
            await GitUtil.clone(repoUrl, branch, targetDir, cloneOptions);
        }
        if (appNames.length > 0) {
            await this.projectOpsFile.writeList('support.app.names', appNames);
        }


    },

    async initPlan(id) {
        this.logger.info("发布计划模式 id:", id);
    },

    isFeishuStoryUrl(rawUrl) {
        try {
            const parsed = new URL(rawUrl);
            return parsed.hostname.includes('feishu') && parsed.pathname.includes('/story/detail/');
        } catch (e) {
            return false;
        }
    },

    parseFeishuStoryUrl(rawUrl) {
        const parsed = new URL(rawUrl);
        const segments = parsed.pathname.split('/').filter(Boolean);
        if (segments.length < 4) {
            throw new Error('飞书需求链接格式不符合预期');
        }
        const [projectKey, flowType, detailKey, storyId] = segments;
        if (detailKey !== 'detail') {
            throw new Error('飞书需求链接缺少 detail 段');
        }
        return {
            origin: parsed.origin,
            projectKey,
            flowType,
            storyId
        };
    },

    readGlobalString(key, fallback = '') {
        try {
            const value = this.globalOpsFile?.read(key);
            if (StrUtil.notNull(value)) {
                return String(value).trim();
            }
        } catch (e) {
            this.logger.debug?.(`读取全局配置 ${key} 失败: ${e.message}`);
        }
        return fallback;
    },

    getStoryDefaultList(key, fallback = []) {
        try {
            const list = this.globalOpsFile?.readList(key) || [];
            const normalized = list.map(item => item.trim()).filter(item => item);
            return CollUtil.isEmptyArray(normalized) ? [...fallback] : normalized;
        } catch (e) {
            this.logger.debug?.(`读取全局列表 ${key} 失败: ${e.message}`);
            return [...fallback];
        }
    },

    normalizeAccountInput(input, fallback = []) {
        if (!input) {
            return [...fallback];
        }
        const list = input.split(',').map(item => item.trim()).filter(item => item);
        if (CollUtil.isEmptyArray(list)) {
            return [...fallback];
        }
        return list;
    },

    normalizeString(value) {
        if (value === undefined || value === null) {
            return '';
        }
        return String(value).trim();
    },

    splitInitExtraArgs(extraArgs = []) {
        const positional = [];
        const named = {};
        if (!Array.isArray(extraArgs)) {
            return {positional, named};
        }
        for (const raw of extraArgs) {
            const value = this.normalizeString(raw);
            if (!value) {
                continue;
            }
            const eqIndex = value.indexOf('=');
            if (eqIndex > 0 && eqIndex < value.length - 1) {
                const key = value.slice(0, eqIndex).trim().toLowerCase();
                const val = value.slice(eqIndex + 1).trim();
                if (key) {
                    named[key] = val;
                    continue;
                }
            }
            positional.push(value);
        }
        return {positional, named};
    },

    extractFlowParamHints(namedArgs = {}) {
        const pickValue = (keys) => {
            for (const key of keys) {
                if (StrUtil.notNull(namedArgs[key])) {
                    return namedArgs[key];
                }
            }
            return '';
        };
        const flowIdKeys = ['flow_id', 'flowid', 'id', 'fid'];
        const flowTypeKeys = ['flow_type', 'flowtype', 'type', 'ft'];
        return {
            flowId: this.normalizeString(pickValue(flowIdKeys)),
            flowType: this.normalizeString(pickValue(flowTypeKeys))
        };
    },

    isOpsFlowDetailUrl(target) {
        try {
            const parsed = target instanceof URL ? target : new URL(target);
            return parsed.pathname.includes('/flow/flow_detail');
        } catch (e) {
            return false;
        }
    },

    logMissingFlowParamHint(missingParams) {
        if (!missingParams?.length) {
            return;
        }
        const params = missingParams.join('、');
        this.logger.warn(`链接中缺少 ${params} 参数，Windows CMD 在未加双引号时会把 & 之后的内容当成新命令。`);
        this.logger.info('建议写法: nb init "https://vops.hszq8.com/app/flow/flow_detail?flow_type=story&flow_id=123456"');
        this.logger.info('也可追加参数: nb init https://vops.hszq8.com/app/flow/flow_detail?flow_type=story flow_id=123456');
    },

    async ensureFlowDetailParams(parsedUrl, overrides = {}) {
        const normalizedOverrides = overrides || {};
        let flowId = this.normalizeString(parsedUrl.searchParams.get('flow_id') || parsedUrl.searchParams.get('id'));
        let flowType = this.normalizeString(parsedUrl.searchParams.get('flow_type') || parsedUrl.searchParams.get('type'));
        if (!flowId && StrUtil.notNull(normalizedOverrides.flowId)) {
            flowId = this.normalizeString(normalizedOverrides.flowId);
        }
        if (!flowType && StrUtil.notNull(normalizedOverrides.flowType)) {
            flowType = this.normalizeString(normalizedOverrides.flowType);
        }
        const missing = [];
        if (!flowId) {
            missing.push('flow_id');
        }
        if (!flowType) {
            missing.push('flow_type');
        }
        if (missing.length) {
            this.logMissingFlowParamHint(missing);
            const questions = [];
            if (!flowId) {
                questions.push({
                    type: 'input',
                    name: 'flowId',
                    message: '请输入发布单 flow_id',
                    validate: input => StrUtil.notNull(input?.trim()) ? true : 'flow_id 不能为空'
                });
            }
            if (!flowType) {
                questions.push({
                    type: 'input',
                    name: 'flowType',
                    message: '请输入 flow_type (story/issue)',
                    default: 'story',
                    validate: input => StrUtil.notNull(input?.trim()) ? true : 'flow_type 不能为空'
                });
            }
            if (questions.length) {
                const answers = await inquirer.prompt(questions);
                if (!flowId) {
                    flowId = this.normalizeString(answers.flowId);
                }
                if (!flowType) {
                    flowType = this.normalizeString(answers.flowType);
                }
            }
        }
        if (!flowId) {
            throw new Error('无法确定 flow_id，请重新输入');
        }
        if (!flowType) {
            flowType = 'story';
        }
        return {flowId, flowType};
    },

    looksLikeUrl(value) {
        const normalized = this.normalizeString(value);
        return /^https?:\/\//i.test(normalized);
    },

    buildStoryBranch(flowType, bu, storyId) {
        const ft = this.normalizeString(flowType) || 'story';
        const business = this.normalizeString(bu) || 'pt';
        const id = this.normalizeString(storyId);
        return `feature-${ft}-${business}-${id}-mid`;
    },

    buildStoryFeatureTag(workType) {
        const wt = this.normalizeString(workType) || '0';
        const randomSuffix = Math.floor(Math.random() * 90000000) + 10000000; // 8 位随机数
        return `feature-${wt}-0-${randomSuffix}`;
    },

    extractStartDevDefaults(flowDetail, flowType, storyId) {
        const defaults = {};
        const flow = flowDetail || {};
        defaults.bu = this.normalizeString(
            flow.bu ??
            flow.business_unit ??
            flow.biz_unit ??
            this.readGlobalString('nb.story.bu', 'pt')
        );
        defaults.zone = this.normalizeString(
            flow.zone ??
            flow.env ??
            this.readGlobalString('nb.story.zone', 'master')
        );
        defaults.workType = this.normalizeString(
            flow.work_type ??
            flow.workType ??
            this.readGlobalString('nb.story.work_type', '0')
        );
        const pipelineCandidate = [
            flow.pipeline_id,
            flow.pipelineId,
            flow.pipeline?.id,
            flow.pipeline?.pipeline_id,
            this.readGlobalString('nb.story.pipeline_id')
        ].map(value => this.normalizeString(value)).find(value => value);
        defaults.pipelineId = pipelineCandidate || '';
        defaults.featureTag = this.normalizeString(
            flow.feature_tag ??
            flow.featureTag ??
            this.readGlobalString('nb.story.feature_tag')
        );
        if (!defaults.featureTag) {
            defaults.featureTag = this.buildStoryFeatureTag(defaults.workType);
        }
        defaults.branch = this.buildStoryBranch(flowType, defaults.bu, storyId);
        return defaults;
    },

    buildFlowDetailUrl(flowId, flowType) {
        const id = this.normalizeString(flowId);
        const type = this.normalizeString(flowType) || 'story';
        return `https://vops.hszq8.com/app/flow/flow_detail?flow_id=${id}&flow_type=${type}`;
    },

    async handleFeishuStoryInit(feishuUrl, appArgs, options = {}) {
        const apps = (appArgs || []).map(item => String(item).trim()).filter(item => item);
        if (CollUtil.isEmptyArray(apps)) {
            this.logger.error('飞书需求模式必须至少提供一个应用名，例如: nb init <飞书链接> hs-app');
            return;
        }
        let storyInfo;
        try {
            storyInfo = this.parseFeishuStoryUrl(feishuUrl);
        } catch (error) {
            this.logger.error(`飞书需求链接解析失败: ${error.message}`);
            throw error;
        }
        const {projectKey, flowType, storyId} = storyInfo;
        if (!projectKey || !storyId) {
            throw new Error('无法从飞书链接中解析项目或需求ID');
        }
        await this.initClient(false);
        const defaultDevManagers = this.getStoryDefaultList('nb.story.dev_manager', this.opsUser ? [this.opsUser] : []);
        const defaultTestManagers = this.getStoryDefaultList('nb.story.test_manager');
        const defaultDevOwners = this.getStoryDefaultList('nb.story.dev_owner', defaultDevManagers);
        const defaultTestOwners = this.getStoryDefaultList('nb.story.test_owner', defaultTestManagers);
        const defaultDevPortLabel = this.readGlobalString('nb.story.dev_port_label', '中台开发');

        const createAnswers = await inquirer.prompt([
            {
                type: 'input',
                name: 'workItemName',
                message: '请输入飞书需求标题（发布单名称）',
                validate: input => StrUtil.notNull(input?.trim()) ? true : '发布单名称不能为空'
            },
            {
                type: 'input',
                name: 'devPortLabel',
                message: '请输入开发线标签',
                default: defaultDevPortLabel,
                validate: input => StrUtil.notNull(input?.trim()) ? true : '开发线标签不能为空'
            },
            {
                type: 'input',
                name: 'devManagers',
                message: '请输入开发负责人账号（多个使用逗号分隔）',
                default: defaultDevManagers.join(',')
            },
            {
                type: 'input',
                name: 'testManagers',
                message: '请输入测试负责人账号（多个使用逗号分隔）',
                default: defaultTestManagers.join(',')
            }
        ]);

        const storyNumericId = Number(storyId);
        const feishuWorkItemId = Number.isFinite(storyNumericId) ? storyNumericId : storyId;
        const devManagers = this.normalizeAccountInput(createAnswers.devManagers, defaultDevManagers);
        const testManagers = this.normalizeAccountInput(createAnswers.testManagers, defaultTestManagers);
        const createPayload = {
            flow_type: flowType || 'story',
            project_key: projectKey,
            feishu_work_item_id: feishuWorkItemId,
            work_item_name: createAnswers.workItemName.trim(),
            dev_port_label: createAnswers.devPortLabel.trim(),
            version: [],
            dev_owner: this.normalizeAccountInput(null, defaultDevOwners),
            dev_manager: devManagers,
            test_owner: this.normalizeAccountInput(null, defaultTestOwners),
            test_manager: testManagers
        };

        this.logger.info(`正在创建发布单，项目: ${projectKey}, 需求ID: ${storyId}`);
        const flowId = await this.opsClient.createFlow(createPayload);
        const flowIdStr = this.normalizeString(flowId);
        this.logger.success(`发布单创建成功，ID: ${flowIdStr}`);

        const flowDetail = await this.opsClient.get_flow_detail(flowIdStr, flowType || 'story');
        const startDefaults = this.extractStartDevDefaults(flowDetail, flowType || 'story', storyId);
        this.logger.info(`新增发布单默认参数: BU=${startDefaults.bu}, Zone=${startDefaults.zone}, Pipeline=${startDefaults.pipelineId}`);

        const startAnswers = await inquirer.prompt([
            {
                type: 'input',
                name: 'bu',
                message: '请输入业务线(bu)',
                default: startDefaults.bu,
                validate: input => StrUtil.notNull(input?.trim()) ? true : 'bu 不能为空'
            },
            {
                type: 'input',
                name: 'zone',
                message: '请输入分区(zone)',
                default: startDefaults.zone,
                validate: input => StrUtil.notNull(input?.trim()) ? true : 'zone 不能为空'
            },
            {
                type: 'input',
                name: 'workType',
                message: '请输入 work_type',
                default: startDefaults.workType || '0',
                validate: input => StrUtil.notNull(input?.trim()) ? true : 'work_type 不能为空'
            },
            {
                type: 'input',
                name: 'pipelineId',
                message: '请输入 pipeline_id',
                default: startDefaults.pipelineId,
                validate: input => StrUtil.notNull(input?.trim()) ? true : 'pipeline_id 不能为空'
            },
            {
                type: 'input',
                name: 'featureTag',
                message: '请输入 feature_tag',
                default: startDefaults.featureTag,
                validate: input => StrUtil.notNull(input?.trim()) ? true : 'feature_tag 不能为空'
            },
            {
                type: 'input',
                name: 'branch',
                message: '请输入自定义分支名(f_branch)',
                default: startDefaults.branch,
                validate: input => StrUtil.notNull(input?.trim()) ? true : '分支名称不能为空'
            }
        ]);

        const startPayload = {
            id: Number(flowIdStr) || flowIdStr,
            type: flowType || 'story',
            work_type: startAnswers.workType.trim(),
            bu: startAnswers.bu.trim(),
            zone: startAnswers.zone.trim(),
            pipeline_id: startAnswers.pipelineId.trim(),
            apps,
            f_branch: startAnswers.branch.trim(),
            feature_tag: startAnswers.featureTag.trim(),
            child: []
        };

        this.logger.info(`转开发参数: ${JSON.stringify(startPayload)}`);
        await this.opsClient.startDev(startPayload);
        this.logger.success('发布单已成功转开发');

        const flowUrl = this.buildFlowDetailUrl(flowIdStr, flowType || 'story');
        this.logger.info(`请在浏览器中确认发布单: ${flowUrl}`);

        const {confirmInit} = await inquirer.prompt([
            {
                type: 'confirm',
                name: 'confirmInit',
                message: `是否继续执行 nb init ${flowUrl} ?`,
                default: true
            }
        ]);
        if (confirmInit) {
            await this.handleInitCommand(flowUrl, [], options);
        } else {
            this.logger.warn('已跳过自动拉取，可手动执行 nb init 完成后续流程');
        }
    },

    async handleInitCommand(target, extraArgs = [], options = {}) {
        const url = typeof target === 'string' ? target.trim() : String(target);
        this.logger.step(1, `初始化发布单，URL: ${url}`);
        const extras = Array.isArray(extraArgs) ? extraArgs : [extraArgs].filter(item => item);
        const cloneOptions = this.attachPermissionCollector(this.buildGitCloneOptions(options));
        try {
            // 验证URL格式 - 支持更宽松的格式
            if (!url) {
                this.logger.error('请提供URL地址');
                this.logger.info('使用示例: nb init http://example.com [目录名]');
                return;
            }
            if (cloneOptions.usePartialClone) {
                this.logger.info('已启用快速克隆模式 (--single-branch --depth=1 --filter=blob:none)');
            }
            if (cloneOptions.targetProtocol === 'ssh') {
                this.logger.info('已启用 SSH 协议强制转换，非 SSH 地址将自动转换');
            } else if (cloneOptions.targetProtocol === 'http') {
                this.logger.info('已启用 HTTP 协议强制转换，仓库地址将使用 HTTP 形式克隆');
            }
            if (this.isFeishuStoryUrl(url)) {
                await this.handleFeishuStoryInit(url, extras, options);
                return;
            }
            // 简单的URL格式检查
            const urlPattern = /^https?:\/\/.+/;
            if (!urlPattern.test(url)) {
                this.logger.warn(`完整发布单、发布计划、依赖发布单URL格式可能不正确: ${url}`);
                this.logger.info('建议使用完整发布单、发布计划、依赖发布单格式，如:  https://example.com');
                return;
            }
            await this.initClient(false);
            const {positional: extraPositionals, named: extraNamed} = this.splitInitExtraArgs(extras);
            const directory = extraPositionals.length > 0 ? extraPositionals[0] : undefined;
            const u = new URL(url);
            if (this.isOpsFlowDetailUrl(u)) {
                const flowOverrides = this.extractFlowParamHints(extraNamed);
                const {flowId, flowType} = await this.ensureFlowDetailParams(u, flowOverrides);
                const normalizedFlowUrl = this.buildFlowDetailUrl(flowId, flowType);
                await this.initNormal(normalizedFlowUrl, flowId, flowType, directory, cloneOptions);
                return;
            }
            // 2. 依赖发布单模式
            if (url.includes("release/deps")) {
                const parts = u.pathname.split("/");
                await this.initDep(url, parts[parts.length - 1], directory, cloneOptions);
                return;
            }
            // 3. 发布计划模式
            if (url.includes("plan_detail")) {
                this.logger.info("发布计划模式 id:", u.searchParams.get("id"));
                await this.initPlan(u.searchParams.get("id"))
                return;
            }
            // 不支持的 URL
            this.logger.error(`不支持的链接 URL: ${url}`);
            throw new Error(`不支持的 URL: ${url}`);
        } catch (error) {
            this.logger.error('初始化失败:', error.message);
            throw error;
        } finally {
            await this.promptPermissionApplyIfNeeded();
            this.flushPendingIdeaOpen();
        }
    },
};
